package com.siao.net.socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.channels.ShutdownChannelGroupException;
import java.util.concurrent.Executors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author SiaoLeon
 * Socket客户端
 */
public class SocketClient extends ConnectionService {

	private static final Logger logger = LoggerFactory.getLogger(SocketClient.class);

	/**
	 * Socket地址
	 */
	private InetSocketAddress address;

	/**
	 * 获取Socket地址
	 * @return
	 */
	public InetSocketAddress getAddress() {
		return this.address;
	}

	/**
	 * Socket通道组
	 */
	private AsynchronousChannelGroup channelGroup;

	/**
	 * Socket通道
	 */
	private AsynchronousSocketChannel channel;

	/**
	 * Socket连接
	 */
	private Connection connection;

	/**
	 * 构造函数
	 */
	public SocketClient() {
		this(null);
	}

	/**
	 * 构造函数
	 * @param bufferSize
	 */
	public SocketClient(int bufferSize) {
		this(null, bufferSize);
	}

	/**
	 * 构造函数
	 * @param address
	 */
	public SocketClient(InetSocketAddress address) {
		this(address, 2048);
	}

	/**
	 * 构造函数
	 * @param address
	 * @param bufferSize
	 */
	public SocketClient(InetSocketAddress address, int bufferSize) {
		this.address = address;
		this.bufferSize = bufferSize;
	}

	/**
	 * 连接
	 */
	public final void connect() {
		this.connect(this.address);
	}

	/**
	 * 连接
	 * @param address
	 */
	public final void connect(InetSocketAddress address) {
		this.connect(address, 3000);
	}

	/**
	 * 连接
	 * @param address
	 * @param timeout
	 */
	public final void connect(InetSocketAddress address, int timeout) {
		try {
			this.address = address;
			if (this.channelGroup == null || this.channelGroup.isShutdown()) {
				this.channelGroup = AsynchronousChannelGroup.withThreadPool(Executors.newSingleThreadExecutor());
			}

			synchronized (this) {
				if (this.connection != null && this.connection.isConnected()) {
					Connection current = this.connection;
					current.close();
				}

				this.connection = new Connection(this);

				this.channel = AsynchronousSocketChannel.open(this.channelGroup);
				this.channel.setOption(StandardSocketOptions.TCP_NODELAY, true);
				this.channel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
				this.channel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
				this.channel.connect(this.address, this.connection, new ConnectionAcceptHandler());

				this.wait(timeout);
			}
		} catch (InterruptedException e) {
			this.close();
		} catch (IOException e) {
			logger.error("SocketClient have failed to connect: " + this.address + "," + e.getMessage());
		}
	}

	/**
	 * 发送数据
	 * @param data
	 */
	public final void send(IDataPack data) {
		this.connection.send(data);
	}

	/**
	 * 关闭
	 */
	public final void close() {
		if (this.connection != null) {
			this.connection.close();
		}
	}

	/**
	 * 断开连接
	 */
	private final void disconnect() {
		try {
			if (this.channel != null) {
				if (this.channel.isOpen()) {
					this.channel.close();
				}
				this.channel = null;
			}

			if (this.channelGroup != null) {
				if (!this.channelGroup.isShutdown()) {
					this.channelGroup.shutdown();
				}
				this.channelGroup = null;
			}
		} catch (IOException e) {
			logger.info("SocketClient have failed to close.");
		}
	}

	/**
	 * @author SiaoLeon
	 * 连接处理
	 */
	class ConnectionAcceptHandler implements CompletionHandler<Void, Connection> {

		@Override
		public void completed(Void result, Connection connection) {
			connection.register(SocketClient.this.channel);

			synchronized (SocketClient.this) {
				SocketClient.this.notifyAll();
			}
		}

		@Override
		public void failed(Throwable exc, Connection connection) {
			if (exc instanceof ShutdownChannelGroupException) {
				logger.error("ShutdownChannelGroupException");
				return;
			}

			SocketClient.this.connectionFailed(connection);
			SocketClient.this.close();

			exc.printStackTrace();
			logger.error(exc.getMessage());

			synchronized (SocketClient.this) {
				SocketClient.this.notifyAll();
			}
		}

	}

	@Override
	protected void connectionRegister(Connection connection) {
		super.connectionRegister(connection);
	}

	@Override
	protected void connectionFailed(Connection connection) {
		super.connectionFailed(connection);
	}

	@Override
	protected void connectionSend(Connection connection, IDataPack data) {
		super.connectionSend(connection, data);
	}

	@Override
	protected void connectionReceive(Connection connection, Object data) {
		super.connectionReceive(connection, data);
	}

	@Override
	protected void connectionClose(Connection connection) {
		super.connectionClose(connection);
	}

	@Override
	protected void connectionDisconnect(Connection connection) {
		super.connectionDisconnect(connection);

		synchronized (this) {
			if (this.connection == connection) {
				this.disconnect();
			}
		}
	}

}
